package com.example.common.soundutil;

import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;

import com.example.common.soundutil.listener.RecordDataListener;
import com.example.common.soundutil.listener.RecordFftDataListener;
import com.example.common.soundutil.listener.RecordResultListener;
import com.example.common.soundutil.listener.RecordSoundSizeListener;
import com.example.common.soundutil.listener.RecordStateListener;
import com.example.common.soundutil.mp3.Mp3EncodeThread;
import com.example.common.soundutil.util.ByteUtils;
import com.example.common.soundutil.util.FFT;
import com.example.common.soundutil.util.FileUtils;
import com.example.common.soundutil.wav.WavUtils;
import com.example.common.tools.LogUtils;
import com.example.common.tools.TooldUtils;


import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;


/**
 * @author zhaolewei on 2018/7/10.
 */
public class RecordHelper {
    private static final String TAG = RecordHelper.class.getSimpleName();
    private volatile static RecordHelper instance;
    private volatile RecordState state = RecordState.IDLE;
    private static final int RECORD_AUDIO_BUFFER_TIMES = 1;

    private RecordStateListener recordStateListener;
    private RecordDataListener recordDataListener;
    private RecordSoundSizeListener recordSoundSizeListener;
    private RecordResultListener recordResultListener;
    private RecordFftDataListener recordFftDataListener;
    private RecordConfig currentConfig;
    private AudioRecordThread audioRecordThread;
    private Handler mainHandler = new Handler(Looper.getMainLooper());

    private File resultFile = null;
    private File tmpFile = null;
    private List<File> files = new ArrayList<>();
    private Mp3EncodeThread mp3EncodeThread;

    private RecordHelper() {
    }

    static RecordHelper getInstance() {
        if (instance == null) {
            synchronized (RecordHelper.class) {
                if (instance == null) {
                    instance = new RecordHelper();
                }
            }
        }
        return instance;
    }

    RecordState getState() {
        return state;
    }

    void setRecordStateListener(RecordStateListener recordStateListener) {
        this.recordStateListener = recordStateListener;
    }

    void setRecordDataListener(RecordDataListener recordDataListener) {
        this.recordDataListener = recordDataListener;
    }

    void setRecordSoundSizeListener(RecordSoundSizeListener recordSoundSizeListener) {
        this.recordSoundSizeListener = recordSoundSizeListener;
    }

    void setRecordResultListener(RecordResultListener recordResultListener) {
        this.recordResultListener = recordResultListener;
    }

    public void setRecordFftDataListener(RecordFftDataListener recordFftDataListener) {
        this.recordFftDataListener = recordFftDataListener;
    }

    public void start(String filePath, RecordConfig config) {
        this.currentConfig = config;
        if (state != RecordState.IDLE) {
            LogUtils.INSTANCE.e(TAG, "状态异常当前状态： %s" + state.name());
            return;
        }
        LogUtils.INSTANCE.e(TAG, "filePath ====  " + filePath);
        resultFile = new File(filePath);
        String tempFilePath = getTempFilePath();

        LogUtils.INSTANCE.d(TAG, "----------------开始录制 ------------------------" + currentConfig.getFormat().name());
        LogUtils.INSTANCE.d(TAG, "参数： == " + currentConfig.toString());
        LogUtils.INSTANCE.i(TAG, "pcm缓存 tmpFile: == " + tempFilePath);
        LogUtils.INSTANCE.i(TAG, "录音文件 resultFile: == " + filePath);


        tmpFile = new File(tempFilePath);
        audioRecordThread = new AudioRecordThread();
        audioRecordThread.start();
    }

    public void stop() {
        if (state == RecordState.IDLE) {
            LogUtils.INSTANCE.e(TAG, "状态异常当前状态： %s" + state.name());
            return;
        }

        if (state == RecordState.PAUSE) {
            makeFile();
            state = RecordState.IDLE;
        } else {
            state = RecordState.STOP;
        }
        notifyState();
    }

    void pause() {
        if (state != RecordState.RECORDING) {
            LogUtils.INSTANCE.e(TAG, "状态异常当前状态： %s" + state.name());
            return;
        }
        state = RecordState.PAUSE;
        notifyState();
    }

    void resume() {
        if (state != RecordState.PAUSE) {
            LogUtils.INSTANCE.e(TAG, "状态异常当前状态： %s" + state.name());
            return;
        }
        String tempFilePath = getTempFilePath();
        LogUtils.INSTANCE.i(TAG, "临时文件目录 =  : %s" + tempFilePath);
        tmpFile = new File(tempFilePath);
        audioRecordThread = new AudioRecordThread();
        audioRecordThread.start();
    }

    private void notifyState() {
        if (recordStateListener == null) {
            return;
        }
        mainHandler.post(new Runnable() {
            @Override
            public void run() {
                recordStateListener.onStateChange(state);
            }
        });

        if (state == RecordState.STOP || state == RecordState.PAUSE) {
            if (recordSoundSizeListener != null) {
                recordSoundSizeListener.onSoundSize(0);
            }
        }
    }

    private void notifyFinish() {
        LogUtils.INSTANCE.d(TAG, "录音结束 file: %s" + resultFile.getAbsolutePath());

        mainHandler.post(new Runnable() {
            @Override
            public void run() {
                if (recordStateListener != null) {
                    recordStateListener.onStateChange(RecordState.FINISH);
                }
                if (recordResultListener != null) {
                    recordResultListener.onResult(resultFile);
                }
            }
        });
    }

    private void notifyError(final String error) {
        if (recordStateListener == null) {
            return;
        }
        mainHandler.post(new Runnable() {
            @Override
            public void run() {
                recordStateListener.onError(error);
            }
        });
    }

    private void notifyData(final byte[] data) {
        if (recordDataListener == null && recordSoundSizeListener == null) {
            return;
        }
        mainHandler.post(new Runnable() {
            @Override
            public void run() {
                if (recordDataListener != null) {
                    recordDataListener.onData(data);
                }

                if (recordFftDataListener != null) {
                    byte[] fftData = makeData(data);
                    if (fftData != null) {
                        if (recordSoundSizeListener != null) {
                            recordSoundSizeListener.onSoundSize(getDb(fftData));
                        }
                        recordFftDataListener.onFftData(fftData);
                    }
                }

            }
        });
    }

    private byte[] makeData(byte[] data) {//data.length = 1280
        if (data.length < 1024) {
            return null;
        }
        try {
            double[] ds = toDouble(ByteUtils.toShorts(data));

            double[] fft = FFT.fft(ds, 62);
            //start
            double[] newFft = new double[128];
            for (int i = 16; i < 16 + newFft.length; i++) {
                if (i < 24) {
                    newFft[i - 16] = fft[i] * 0.2;
                } else if (i < 36) {
                    newFft[i - 16] = fft[i] * 0.4;
                } else if (i < 48) {
                    newFft[i - 16] = fft[i] * 0.6;
                } else {
                    newFft[i - 16] = fft[i];
                }
                if (newFft[i - 16] < 10 * 128) {
                    newFft[i - 16] = newFft[i - 16] * 0.6;
                }
            }
            fft = newFft;
            //end
            int step = fft.length / 128;
            byte[] fftBytes = new byte[128];


            int scale = 128;//压缩128基准
            double max = getMax(fft);
            if (max > 128 * 128) {//高音优化
                scale = (int) (max / 128) + 2;
            }

            for (int i = 0; i < fftBytes.length; i++) {
                double tmp = fft[i * step] / scale;
                if (tmp > 127) {
                    fftBytes[i] = 127;
                } else if (tmp < -128) {
                    fftBytes[i] = -127;

                } else {
                    fftBytes[i] = (byte) tmp;
                }
            }
            return fftBytes;
        } catch (Exception e) {
            LogUtils.INSTANCE.w(TAG, e.getMessage());
        }
        return null;
    }

    private double getMax(double[] data) {
        double max = 0;
        for (int i = 0; i < data.length; i++) {
            if (data[i] > max) {
                max = data[i];
            }
        }

        return max;
    }

    public int getDb(byte[] data) {
        double sum = 0;
        double ave;
        int length = data.length > 128 ? 128 : data.length;
        for (int i = 0; i < length; i++) {
            sum += data[i];
        }
        ave = sum / length;
        sum += (Math.pow(ave, 4) / Math.pow((128F - ave), 2));
        int i = (int) (Math.log10((sum / length) * 53536F) * 10);
        return i < 0 ? 27 : i;
    }

    private double[] toDouble(short[] bytes) {
        int length = 512;
        double[] ds = new double[length];
        for (int i = 0; i < length; i++) {
            ds[i] = bytes[i];
        }
        return ds;
    }

    private void initMp3EncoderThread(int bufferSize) {
        try {
            mp3EncodeThread = new Mp3EncodeThread(resultFile, bufferSize);
            mp3EncodeThread.start();
        } catch (Exception e) {
            LogUtils.INSTANCE.e(TAG, e.getMessage());
        }
    }

    private class AudioRecordThread extends Thread {
        private AudioRecord audioRecord;
        private int bufferSize;

        AudioRecordThread() {
            bufferSize = AudioRecord.getMinBufferSize(currentConfig.getSampleRate(),
                    currentConfig.getChannelConfig(), currentConfig.getEncodingConfig()) * RECORD_AUDIO_BUFFER_TIMES;
            LogUtils.INSTANCE.d(TAG, "record buffer size = %s" + bufferSize);
            audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, currentConfig.getSampleRate(),
                    currentConfig.getChannelConfig(), currentConfig.getEncodingConfig(), bufferSize);
            if (currentConfig.getFormat() == RecordConfig.RecordFormat.MP3) {
                if (mp3EncodeThread == null) {
                    initMp3EncoderThread(bufferSize);
                }
            }
        }

        @Override
        public void run() {
            super.run();

            switch (currentConfig.getFormat()) {
                case MP3:
                    startMp3Recorder();
                    break;
                default:
                    startPcmRecorder();
                    break;
            }
        }

        private void startPcmRecorder() {
            state = RecordState.RECORDING;
            notifyState();
            LogUtils.INSTANCE.eLog(TAG, "开始录制-------> Pcm");
            FileOutputStream fos = null;
            try {

                fos = new FileOutputStream(tmpFile);
                audioRecord.startRecording();
                byte[] byteBuffer = new byte[bufferSize];

                while (state == RecordState.RECORDING) {
                    int end = audioRecord.read(byteBuffer, 0, byteBuffer.length);
                    notifyData(byteBuffer);
                    fos.write(byteBuffer, 0, end);
                    fos.flush();
                }
                audioRecord.stop();
                files.add(tmpFile);
                if (state == RecordState.STOP) {
                    makeFile();
                } else {
                    LogUtils.INSTANCE.i(TAG, "暂停！");
                }
            } catch (Exception e) {
//                PgyerSDKManager.reportException(e);
                LogUtils.INSTANCE.eLog(TAG, "录音失败--Pcm------->"+e.getMessage());
                notifyError("录音失败");
            } finally {
                try {
                    if (fos != null) {
                        fos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (state != RecordState.PAUSE) {
                state = RecordState.IDLE;
                notifyState();
                LogUtils.INSTANCE.d(TAG, "录音结束");
            }
        }

        private void startMp3Recorder() {
            state = RecordState.RECORDING;
            notifyState();

            try {
                audioRecord.startRecording();
                short[] byteBuffer = new short[bufferSize];

                while (state == RecordState.RECORDING) {
                    int end = audioRecord.read(byteBuffer, 0, byteBuffer.length);
                    if (mp3EncodeThread != null) {
                        mp3EncodeThread.addChangeBuffer(new Mp3EncodeThread.ChangeBuffer(byteBuffer, end));
                    }
                    notifyData(ByteUtils.toBytes(byteBuffer));
                }
                audioRecord.stop();
            } catch (Exception e) {
                LogUtils.INSTANCE.e(TAG, e.getMessage());
                notifyError("录音失败");
            }
            if (state != RecordState.PAUSE) {
                state = RecordState.IDLE;
                notifyState();
                if (mp3EncodeThread != null) {
                    mp3EncodeThread.stopSafe(new Mp3EncodeThread.EncordFinishListener() {
                        @Override
                        public void onFinish() {
                            notifyFinish();
                            mp3EncodeThread = null;
                        }
                    });
                } else {
                    notifyFinish();
                }
            } else {
                LogUtils.INSTANCE.d(TAG, "暂停");
            }
        }
    }

    private void makeFile() {
        switch (currentConfig.getFormat()) {
            case MP3:
                return;
            case WAV:
                mergePcmFile();
                makeWav();
                break;
            case PCM:
                mergePcmFile();
                break;
            default:
                break;
        }
        notifyFinish();
        LogUtils.INSTANCE.i(TAG, "录音完成！ path:   " + resultFile.getAbsoluteFile() +"  大小 =  "+ resultFile.length());
    }

    /**
     * 添加Wav头文件
     */
    private void makeWav() {
        if (!FileUtils.isFile(resultFile) || resultFile.length() == 0) {
            return;
        }
        byte[] header = WavUtils.generateWavFileHeader((int) resultFile.length(), currentConfig.getSampleRate(), currentConfig.getChannelCount(), currentConfig.getEncoding());
        WavUtils.writeHeader(resultFile, header);
    }

    /**
     * 合并文件
     */
    private void mergePcmFile() {
        boolean mergeSuccess = mergePcmFiles(resultFile, files);
        if (!mergeSuccess) {
            notifyError("合并失败");
        }
    }

    /**
     * 合并Pcm文件
     *
     * @param recordFile 输出文件
     * @param files      多个文件源
     * @return 是否成功
     */
    private boolean mergePcmFiles(File recordFile, List<File> files) {
        if (recordFile == null || files == null || files.size() <= 0) {
            return false;
        }

        FileOutputStream fos = null;
        BufferedOutputStream outputStream = null;
        byte[] buffer = new byte[1024];
        try {
            fos = new FileOutputStream(recordFile);
            outputStream = new BufferedOutputStream(fos);

            for (int i = 0; i < files.size(); i++) {
                BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(files.get(i)));
                int readCount;
                while ((readCount = inputStream.read(buffer)) > 0) {
                    outputStream.write(buffer, 0, readCount);
                }
                inputStream.close();
            }
        } catch (Exception e) {
            LogUtils.INSTANCE.e(TAG, e.getMessage());
//            PgyerSDKManager.reportException(e);
            return false;
        } finally {
            try {
                if (fos != null) {
                    fos.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        for (int i = 0; i < files.size(); i++) {
            files.get(i).delete();
        }
        files.clear();
        return true;
    }

    /**
     * 根据当前的时间生成相应的文件名
     * 实例 record_20160101_13_15_12
     */
    private String getTempFilePath() {

        String fileDir = TooldUtils.INSTANCE.getRingtonesPath();
        if (!FileUtils.createOrExistsDir(fileDir)) {
            LogUtils.INSTANCE.e(TAG, "文件夹创建失败：%s" + fileDir);
            fileDir = TooldUtils.INSTANCE.getApplication().getCacheDir().getAbsolutePath();
            LogUtils.INSTANCE.e(TAG, "文件夹创建失败：%s" + fileDir);
        }
        String fileName = String.format(Locale.getDefault(), "record_tmp_%s", FileUtils.getNowString(new SimpleDateFormat("yyyyMMdd_HH_mm_ss", Locale.SIMPLIFIED_CHINESE)));
        return String.format(Locale.getDefault(), "%s%s.pcm", fileDir + "/", fileName);
    }

    /**
     * 表示当前状态
     */
    public enum RecordState {
        /**
         * 空闲状态
         */
        IDLE,
        /**
         * 录音中
         */
        RECORDING,
        /**
         * 暂停中
         */
        PAUSE,
        /**
         * 正在停止
         */
        STOP,
        /**
         * 录音流程结束（转换结束）
         */
        FINISH
    }

}

